package game.gameobject.misc;

import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.joints.PhysicsJoint;
import com.jme3.bullet.joints.Point2PointJoint;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Cylinder;
import java.util.ArrayList;
import java.util.List;

/** Simulates a rope by keeping a collection of positioned and joined links inside it. */
public class Rope extends Node {
    private List<Geometry> links = new ArrayList<Geometry>();
    private Material linkMaterial;
    private float length, thicknessRadius, lengthPerLink, mass;
    private int axisSamples, radialSamples;

    public Rope(float length, float thicknessRadius, Material material) {
	this(Vector3f.ZERO, length, thicknessRadius, material);
    }
    
    /** Creates a rope hanging straight down from startPoint. */
    public Rope(Vector3f startPoint, float length, float thicknessRadius, Material material) {
	super("rope");
	
        this.thicknessRadius = thicknessRadius;
        this.length = length;
        this.linkMaterial = material;

        initDefaultValues();
	calcLengthPerLink();
        createRope(startPoint);
    }
    
    /** Creates a rope from startPoint to endPoint, with optional slack between. */
    public Rope(Vector3f startPoint, Vector3f endPoint, float extraSlack, float thicknessRadius, Material material) {
        this(startPoint, endPoint.subtract(startPoint).length()+extraSlack, thicknessRadius, material);

	moveLastLink(endPoint);
    }
    
    private void initDefaultValues() {
	mass = 0.1f;
        axisSamples = 4;
        radialSamples = 8;
    }

    /** Figure out a good length per link for the given length so that we have an integer number of links. */
    private void calcLengthPerLink() {
	float maxLengthPerLink = 0.2f; //arbitray. lower number = better quality but requires more cpu
	float numLinks = FastMath.floor(length/maxLengthPerLink);
	if(numLinks < length)
	    numLinks++;
	lengthPerLink = length/numLinks;
    }
    
    private void createRope(Vector3f startPoint) {
        float currentLength = 0f;
        Vector3f currentCenter = new Vector3f(startPoint);
        Geometry oldLastLink = null;
        while(currentLength < length) {
            if(!links.isEmpty()) {
                oldLastLink = getLastLink();
            }
            createLink(currentCenter);
            if(oldLastLink != null) {
                join(oldLastLink, getLastLink(), new Vector3f(currentCenter.getX(), currentCenter.getY()+lengthPerLink/2, currentCenter.getZ()));
            }
            currentCenter.setY(currentCenter.getY()-lengthPerLink);
            currentLength += lengthPerLink;
        }
    }
    
    private void createLink(Vector3f location) {
        Cylinder linkShape = new Cylinder(axisSamples, radialSamples, thicknessRadius, lengthPerLink, true);
        Geometry link = new Geometry("link", linkShape);
	link.rotateUpTo(Vector3f.UNIT_Z);
        link.setMaterial(linkMaterial);
        link.setLocalTranslation(location);
	this.attachChild(link);
        RigidBodyControl linkControl = new RigidBodyControl(mass);
        link.addControl(linkControl);
	links.add(link);
    }

    /** Creates a joint between the given links at the given world points. */
    private PhysicsJoint join(Spatial A, Spatial B, Vector3f connectionPoint) {
        Vector3f pivotA = A.worldToLocal(connectionPoint, new Vector3f());
        Vector3f pivotB = B.worldToLocal(connectionPoint, new Vector3f());
        Point2PointJoint joint = new Point2PointJoint(A.getControl(RigidBodyControl.class), B.getControl(RigidBodyControl.class), pivotA, pivotB);
	joint.setCollisionBetweenLinkedBodys(false);
	return joint;
    }

    private void moveLastLink(Vector3f location) {
	getLastLink().setLocalTranslation(location);
	//TODO rotate in some way relative to endPoint-startPoint
	getLastLink().rotate(new Quaternion().fromAngleAxis(FastMath.PI, new Vector3f(1,0,0)));
    }
    
    public Geometry getFirstLink() {
        return links.get(0);
    }
    
    public Geometry getLastLink() {
        return links.get(links.size()-1);
    }
    
    public float getLengthPerLink() {
        return lengthPerLink;
    }
}
